Спринт 3/18 → Тема 1/3: Самое необходимое об ООП → Урок 6/8
Работает — не трогай сохрани!
По ходу спринта у вас были задачи, связанные с модулем character_creation_module: вы приводили код модуля в порядок — оформляли его в соответствии с код-стайлом.
В подобных задачах было одно требование, которое мы никак не проверяли: зафиксировать изменения на GitHub. Если вы следовали этому требованию, то в вашем репозитории на GitHub сейчас находится рабочая и приведённая в порядок версия модуля. Она вам пригодится для того, чтобы вы смогли просмотреть рабочий код модуля без классов и вернуться к нему при необходимости.
Если же вы по каким-то причинам проигнорировали требование фиксировать изменения на GitHub, то прямо сейчас отправьте в свой репозиторий модуль character_creation_module, с которым вы работали в течение спринта. С этим вам помогут команды:
BASH
Когда нужная версия модуля character_creation_module будет на GitHub, перейдите в свой репозиторий и откройте файл main.py прямо в браузере, чтобы код модуля был у вас перед глазами. Локально, в редакторе кода, тоже откройте файл main.py из директории character_creation_module и очистите его — вы наполните этот файл с «чистого листа», приведёте код в нём в соответствие с парадигмой ООП.
Эскиз модуля
При разработке нового модуля, независимо от используемой парадигмы, первым делом прорабатывается его структура без деталей, то есть записываются классы и функции, которые предположительно будут использоваться. Это своеобразный план, который помогает не потерять первоначальный замысел программы.
Начнём перерабатывать код модуля character_creation_module. Первым делом оформим структуру: опишем те классы и методы, из которых будет состоять программа.
Сейчас в модуле character_creation_module можно выделить три класса персонажей —
Warrior, Mage и Healer. Персонажи этих классов должны уметь атаковать, защищаться и применять специальный навык, который доступен только им. Умения у всех персонажей одинаковые, но работают они по-разному, именно поэтому в функциях, которые отвечают за способности персонажей, есть сложные if-блоки.Эту логику нужно перенести в классы. То есть для каждого класса нужно определить методы атаки, защиты и специального умения.
Избежать дублирования кода и упростить конструкции поможет наследование. Создадим базовый класс и назовём его
Character и объявим в этом классе общие методы и свойства. Классы Warrior, Mage и Healer будут наследниками класса Character.Откройте файл main.py модуля character_creation_module в редакторе кода и определите в нём структуру с учётом парадигмы ООП — объявите родительский класс
Character и дочерние классы Warrior, Mage и Healer. Вместо тела классов используйте эллипсис. Далее перенесите код в поле сниппета, чтобы проверить себя.КодPYTHON
Базовый класс
Character должен содержать общие атрибуты для всех дочерних классов. Всё, к чему может обратиться экземпляр класса через точечную нотацию, и есть атрибуты класса, например уже знакомые вам свойства и методы. Объявите в базовом классе
Character метод __init__(), который будет принимать значение для свойства name, и методы attack(), defence(), special().Дополните код базового класса в файле main.py:
PYTHON
Эскиз готов! Двигаемся дальше.
Оптимизация исходных функций
Начнём с функции
attack() — функции, которая отвечает за генерацию очков урона, нанесённого противнику. В перерабатываемом коде модуля character_creation_module логика этой функции для разных игровых персонажей описана похожим образом:PYTHON
Что тут одинакового: формат и состав фразы, которая выводится после атаки, а также значение стандартной атаки, оно равно 5.
Отличается лишь интервал для рандомного значения, которое прибавляется к стандартной атаке. У каждого персонажа этот интервал свой.
В методе
attack() класса Character дублирования кода можно избежать. Программа должна возвращать одну и ту же фразу после атаки —
<Имя персонажа> нанёс противнику урон, равный <количество очков урона>. При новом подходе имя должно храниться не в переменной, а в свойстве экземпляра объекта self.name — значит, в конструкторе класса Character нужно объявить его. Диапазон очков урона можно выбрать любой, позже мы переопределим его в классах-наследниках.С учётом изменений ваш код должен выглядеть так:
PYTHON
Диапазон очков урона в дальнейшем нужно будет переопределить для каждого класса-наследника. Если оставить код в таком виде, то переопределять придётся весь метод родительского класса, а это не очень удобно.
Можно поступить иначе — сделать переопределяемым только значение диапазона для функции
randint(). Для этого вынесите диапазон очков урона в виде кортежа в константу RANGE_VALUE_ATTACK и задайте для неё значение от 1 до 3. Также вынесите в отдельную переменную подсчёт очков атаки. Назовите эту переменную
value_attack.Обновите код в файле main.py:
PYTHON
Глобально метод
attack() готов к работе, но есть один момент, который требует внимания — значение стандартной атаки, равное 5. То, что это значение стандартной атаки, знаете только вы. Когда в ваш код заглянет сторонний разработчик, для него будет непонятно, за что отвечает «магическое число» 5.Хорошим тоном считается давать числовым значениям имена, если это возможно. Число 5 — это значение стандартной атаки, и в рамках вашего кода оно неизменяемо. Вынесите это значение в глобальную константу и назовите её
DEFAULT_ATTACK:PYTHON
Теперь с методом
attack() всё в полном порядке. Осталось ещё два метода, логику которых нужно проработать — defence() и special().Начнём с метода защиты —
defence(). Исходный код функции, который вам сейчас предстоит переработать, выглядит так:PYTHON
Вынесите стандартное значение защиты в глобальную константу
DEFAULT_DEFENCE. Она будет равна 10. А диапазон значений защиты вынесите в виде кортежа в переменную класса Character, назовите эту переменную RANGE_VALUE_DEFENCE и задайте ей значение от 1 до 5.Далее по примеру с методом
attack() опишите тело метода defence().Дополните код в файле main.py:
PYTHON
Осталось описать логику работы метода специального умения —
special(). Этот метод должен возвращать:- название умения;
- значение очков урона, которое наносит это умение.
Добавьте две новые константы для класса
Character:SPECIAL_SKILL— название умения, значение —'Удача';SPECIAL_BUFF— значение очков урона, для базового класса, оно будет равно 15.
Далее опишите тело метода
special().В итоге код в файле main.py должен принять такой вид:
PYTHON
Дорабатываем базовый класс
В коде модуля character_creation_module у каждого игрового класса есть описание:
PYTHON
Эту конструкцию из условных операторов тоже можно оптимизировать и унести описание в отдельный метод базового класса. Чтобы напечатать описание класса, воспользуйтесь переопределением стандартного метода
__str__.💡 Где-то это уже было 🤔
В Python все классы напрямую или через классы-родители — наследники встроенного базового класса
object. Все классы в Python могут использовать методы класса object, например, знакомый вам метод __str__() определён именно в классе object.Пусть метод
__str__ возвращает имя класса и описание класса. Чтобы вывести имя у объекта класса есть специальный атрибут __class__.__name__, а для описания создайте ещё одну константу, назовите её BRIEF_DESC_CHAR_CLASS. Задайте ей значение для базового класса — 'отважный любитель приключений'.Полный код базового класса будет таким:
PYTHON
Базовый класс готов! Теперь займёмся дочерними классами.
Классы-наследники
Благодаря тому, что вы объявили общие переменные для базового класса, у вас получилось реализовать универсальные родительские методы. Теперь, описывая классы-наследники, вам нужно всего лишь переопределить переменные родительского класса. Тогда при вызове базовых методов дочерними классами будет возвращаться нужный результат — такой же, как в исходном коде модуля.
Дополните код в файле main.py:
PYTHON
Теперь можно создать объект одного из классов-наследников, например
Warrior, и вызвать для него метод атаки attack(). В терминал выведется результат, который соответствует атаке персонажа «Воин», описанной в классе Warrior:PYTHON